package san

import (
	b "code.google.com/p/gochess/board"
	pos "code.google.com/p/gochess/position"
	"errors"
	"fmt"
	"strings"
)

var notation2Rank = map[string]b.Rank{
	"1": b.Rank1,
	"2": b.Rank2,
	"3": b.Rank3,
	"4": b.Rank4,
	"5": b.Rank5,
	"6": b.Rank6,
	"7": b.Rank7,
	"8": b.Rank8,
}

// ParseRank parses the notation of a rank.
func parseRank(notation string) (b.Rank, error) {

	if rank, ok := notation2Rank[notation]; ok {
		return rank, nil
	}
	return b.NoRank, errors.New("invalid rank notation")

}

var notation2File = map[string]b.File{
	"a": b.A,
	"b": b.B,
	"c": b.C,
	"d": b.D,
	"e": b.E,
	"f": b.F,
	"g": b.G,
	"h": b.H,
}

// ParseFile parses the notation of a file.
func parseFile(notation string) (b.File, error) {

	if file, ok := notation2File[notation]; ok {
		return file, nil
	}
	return b.NoFile, errors.New("invalid file notation")
}

var notation2PieceType = map[string]pos.PieceType{
	"K": pos.King,
	"Q": pos.Queen,
	"R": pos.Rook,
	"B": pos.Bishop,
	"N": pos.Knight,
	"":  pos.Pawn,
}

// ParsePiece parses the notation of a piece.
func ParsePiece(notation string) (pos.PieceType, error) {
	if piece, ok := notation2PieceType[notation]; ok {
		return piece, nil
	}
	return pos.NoPieceType, errors.New("illegal piece notation")

}

var illegalSquare = errors.New("invalid square notation.")

// ParseSquare parses the notation of a square.
func ParseSquare(notation string) (b.Square, error) {

	if len(notation) != 2 {
		return b.NoSquare, illegalSquare
	}
	file, err := parseFile(notation[:1])
	if err != nil {
		return b.NoSquare, illegalSquare
	}
	rank, err := parseRank(notation[1:])
	if err != nil {
		return b.NoSquare, illegalSquare
	}
	return b.GetSquare(file, rank), nil
}

var unused = []string{capture, enPassant, "+", "++", "=", "#"}

func removeUnusedStuff(notation string) string {

	for i := range unused {
		notation = strings.Replace(notation, unused[i], "", -1)
	}
	return strings.TrimSpace(notation)

}

func isValid(move pos.Move, validMoves []pos.Move) bool {

	for i := range validMoves {
		if validMoves[i] == move {
			return true
		}
	}
	return false
}

var defaultError = errors.New("invalid move notation")

// ParseMove parses the notation of a move.
func ParseMove(p *pos.Position, notation string, validMoves []pos.Move) (pos.Move, error) {

	var moves []pos.Move
	player := p.SideToMove()
	notation = strings.TrimSpace(removeUnusedStuff(notation))

	if notation == "0-0" || notation == "O-O" {
		return pos.NewCastling(player, pos.CastlingShort), nil
	}
	if notation == "0-0-0" || notation == "O-O-O" {
		return pos.NewCastling(player, pos.CastlingLong), nil
	}

	switch len(notation) {
	case 2:
		// a pawn move, notation is the destination square
		if dst, err := ParseSquare(notation); err == nil {
			moves = findMoves(pos.Pawn, dst, p, validMoves)
		}
	case 3:
		if movedPiece, err := ParsePiece(notation[:1]); err == nil {
			// first letter is a piece -> last 2 characters is the destination
			if dst, err := ParseSquare(notation[1:]); err == nil {
				moves = findMoves(movedPiece, dst, p, validMoves)
			}
		} else {
			// first character is not a piece, so we have a pawn move
			if promotedPiece, err := ParsePiece(notation[2:]); err == nil {
				// last character is a piece type -> promotion
				if dst, err := ParseSquare(notation[:2]); err == nil {
					start := dst + pos.MoveDirection(pos.Opponent(player))
					moves = append(moves, pos.NewPromotion(start, dst, promotedPiece))
				}
			} else {
				// last option: pawn capture
				// first character starting file, last two characters destination square
				if dst, err := ParseSquare(notation[1:]); err == nil {
					if file, err := parseFile(notation[:1]); err == nil {
						pawnMoves := findMoves(pos.Pawn, dst, p, validMoves)
						for i := range pawnMoves {
							moveFile := b.GetFile(pawnMoves[i].Start())
							if moveFile == file {
								moves = append(moves, pawnMoves[i])
							}
						}
					}
				}
			}
		}
	case 4:
		if movedPiece, err := ParsePiece(notation[:1]); err == nil {
			// first letter is a piece ->
			// second char is file/rank of start square,
			// last 2 characters is the destination
			if dst, err := ParseSquare(notation[2:]); err == nil {
				pieceMoves := findMoves(movedPiece, dst, p, validMoves)
				if file, err := parseFile(notation[1:2]); err == nil {
					// second char is the start file
					for i := range pieceMoves {
						startFile := b.GetFile(pieceMoves[i].Start())
						if startFile == file {
							moves = append(moves, pieceMoves[i])
						}
					}
				}
				if rank, err := parseRank(notation[1:2]); err == nil {
					// second char is rank of start square
					for i := range pieceMoves {
						startRank := b.GetRank(pieceMoves[i].Start())
						if startRank == rank {
							moves = append(moves, pieceMoves[i])
						}
					}
				}
			}
		} else {
			// first char is not a piece, and len(notation) == 4 ->capturing pawn promotes
			// first char is the file of start square,
			// second/third char is the destination, forth is the promoted piece
			if promotedPiece, err := ParsePiece(notation[3:]); err == nil {
				if dst, err := ParseSquare(notation[1:3]); err == nil {
					if startFile, err := parseFile(notation[:1]); err == nil {
						startRank := b.GetRank(dst - pos.MoveDirection(player))
						if startRank == pos.PromotionRank(player) {
							start := b.GetSquare(startFile, startRank)
							if b.IsValid(start) {
								moves = append(moves, pos.NewPromotion(start, dst, promotedPiece))
							} else {
								panic(fmt.Sprintf("Error while getting start square for rank %v and file %v", startRank, startFile))
							}
						} else {
							panic(fmt.Sprintf("Invalid square rank %v of promotion, expected %v", startRank, pos.PromotionRank(player)))
						}
					}
				}
			}
		}
	case 5:
		// only one option here:
		// more than 2 pieces can move to the destination, and we have to know the starting square
		// example: "Qb5c6"
		_, err := ParsePiece(notation[:1])
		if err != nil {
			return pos.NoMove, defaultError
		}
		start, err := ParseSquare(notation[1:3])
		if err != nil || !b.IsValid(start) {
			return pos.NoMove, defaultError
		}
		dst, err := ParseSquare(notation[3:5])
		if err != nil || !b.IsValid(dst) {
			return pos.NoMove, defaultError
		}
		moves = append(moves, pos.NewMove(start, dst))
	default:
		return pos.NoMove, errors.New("too many/too few characters")
	}
	switch len(moves) {
	case 0:
		return pos.NoMove, defaultError
	case 1:
		return moves[0], nil
	default:
		return pos.NoMove, errors.New("more than one possible move")
	}
}
